#region Preset using directives

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;

using VirtualLab.Programming;
using VirtualLabAPI.Core.BasicFunctions;
using VirtualLabAPI.Core.Common;
using VirtualLabAPI.Core.DataVisualization;
using VirtualLabAPI.Core.FieldRepresentations;
using VirtualLabAPI.Core.Functions;
using VirtualLabAPI.Core.GeometryDescription;
using VirtualLabAPI.Core.LightPath;
using VirtualLabAPI.Core.Materials;
using VirtualLabAPI.Core.Numerics;
using VirtualLabAPI.Core.Numerics.Region2D;
using VirtualLabAPI.Core.OpticalSystems;
using VirtualLabAPI.Core.Propagation;
using VirtualLabAPI.Core.SupportFunctions;
using Wyrowski.Infrastructure.Support;
using VirtualLabAPI.Core.ExportImport;
using System.Text;

#endregion

/****************************************************************************

Title: "Export Meta Structure to GDSII and Text"
Author: ChHe (LightTrans)
Description:
As input this VLF module expects an Optical Setup with a configured Pillar Medium, which contains the pillar structure for the export. For the output, the index of the component
and the loacation of the stack have to be configured properly.
 
Version (Date): 1.0 (2023-06-20)
****************************************************************************/

namespace OwnCode {
	public class VLModule {
		//define index of optical setup element
		int indexElement = 1;
		//define whether front stack shall be used or back stack
		bool metaGratingLocatedInFrontStack = false;
		//define the index of the stack layer
		int indexStackLayer = 1;
		//folder name to store the GDSII data
		string folderName = @"D:\GDSII\";
		// file name to store the GDSII data
		string filename = "MetaLens_CircularPillars";
		// define the number of points per circle to generate (only used in case of circular pillars
		int numberOfPointsOnEachCircleForRoundPillars = 101;

		public void Run() {
			//get optical system which contains the meta grating which shall be exported
			Lightpath opticalSetupForExport = DocumentHistory<Lightpath>.BrowseDocuments("Select Grating System for Export");

			//error handling (if no system was selected)
			if (opticalSetupForExport == null) {
				Globals.DataDisplay.LogError("No system selected for export.");
				return;
			}

			//get element
			GratingComponentLPE gratingComponent = (opticalSetupForExport[indexElement] as GratingComponentLPE);
			//check whether grating component is accessible
			if (gratingComponent == null) {
				Globals.DataDisplay.LogError("Selected component not found.");
				return;
			}

			//get optical stack from grating component
			OpticalStack stackWithMetaGrating = null;
			if (metaGratingLocatedInFrontStack) {
				stackWithMetaGrating = gratingComponent.Stack1;
			}
			else {
				stackWithMetaGrating = gratingComponent.Stack2;
			}

			//error handling
			if (stackWithMetaGrating == null) {
				Globals.DataDisplay.LogError("No stack could be found.");
				return;
			}

			//check whether layer index is valid
			if (indexStackLayer < 1 ||
			   indexStackLayer > stackWithMetaGrating.Layers.Count) {
				Globals.DataDisplay.LogError("No valid stack layer selected.");
				return;
			}

			//get stack layer
			StackLayer layer = stackWithMetaGrating.Layers[indexStackLayer - 1];
			//get layer medium as pillar medium general
			PillarMediumGeneral pillarMedium = layer.LayerMedium as PillarMediumGeneral;

			//check whether pillar medium is given
			if (pillarMedium == null) {
				Globals.DataDisplay.LogError("The selected stack layer does not contain a pillar medium.");
				return;
			}

			if (!Directory.Exists(this.folderName)) {
				Directory.CreateDirectory(this.folderName);
			}
			//define filename for the GDSII file
			string fileNameGDSII = Path.Combine(this.folderName, this.filename + ".gds");
			//check whether file is already given
			if (File.Exists(fileNameGDSII)) {
				//delete file
				File.Delete(fileNameGDSII);
			}

			//define size for coordinate transformation
			VectorD size = new VectorD(pillarMedium.MediaPeriod.X, pillarMedium.MediaPeriod.Y);
			SamplingParameters integerSampling;
			double conversionFactor = ExportSupportFunctions.ConversionFactorForIntegerCoordinates(size,
																								   true,
																								   false,
																								   out integerSampling);
			//generate GDII file handler
			GDSIIFileWriter gdsiiFileHandler = new GDSIIFileWriter(Path.Combine(this.folderName, this.filename),
																	conversionFactor,
																	integerSampling);
			//init default structure
			gdsiiFileHandler.InitStructure(false, ExportSupportFunctions.DefaultStructureName);
			//generate string builder
			StringBuilder stringBuilderForASCIIExport = new StringBuilder();

			//loop over all pillar in meta grating medium
			for (int runAllPillars = 0; runAllPillars < pillarMedium.PositionsAndDiameters.Count; runAllPillars++) {
				//get current position
				VectorD positionCurrPillar = pillarMedium.PositionsAndDiameters[runAllPillars].position;
				//get current diameter
				double diameterCurrPillar = pillarMedium.PositionsAndDiameters[runAllPillars].size;

				//add information to string builder
				stringBuilderForASCIIExport.AppendLine(positionCurrPillar.X.ToString() + "\t" + positionCurrPillar.Y.ToString() + "\t" + diameterCurrPillar.ToString());

				//define simple polygon
				SimplePolygon polygonToExport = null;

				if (pillarMedium.PositionsAndDiameters.QuadraticInsteadOfCircular) {
					polygonToExport = generateSquareAsPolygon(positionCurrPillar, diameterCurrPillar);
				}
				else {
					polygonToExport = generateCircleAsPolygon(positionCurrPillar, diameterCurrPillar);
				}
				//convert to int polygons
				int[] polyGonAsInt = convertPolygonToIntegerArray(polygonToExport, conversionFactor);
				try {
					gdsiiFileHandler.CreatePolygon(polyGonAsInt);
				}
				catch (Exception ex) {
					Globals.DataDisplay.LogError("GDSII Generation failed (" + ex.Message + "). ");
					gdsiiFileHandler.EndGroup();
					gdsiiFileHandler.EndFile();
					return;
				}
			}
			gdsiiFileHandler.EndGroup();
			gdsiiFileHandler.EndFile();
			Globals.DataDisplay.LogMessage("GDSII File generated.");

			//generate filename for txt file
			string fileNameTXTFile = Path.Combine(this.folderName, this.filename + ".txt");
			//check whether file exist
			if (File.Exists(fileNameTXTFile)) {
				//if file exist => delete
				File.Delete(fileNameTXTFile);
			}
			//write information to text file
			VL_Files.WriteToLogFile(fileNameTXTFile, stringBuilderForASCIIExport.ToString());
			Globals.DataDisplay.LogMessage("ASCII File generated.");
		}

		/// <summary>
		/// support function to generate a polygon for a round pillar with given diameter and position
		/// </summary>
		/// <param name="positionOfCircle">the position of the circle</param>
		/// <param name="diameterOfCircle">the diameter of the circle</param>
		/// <returns>the circle as a simply polygon</returns>
		private SimplePolygon generateCircleAsPolygon(VectorD positionOfCircle,
													  double diameterOfCircle) {
			PolygonVertexList vertices = new PolygonVertexList();

			//calculate sampling distance in angles
			double samplingDistanceAngle = MathFunctions.TwoPi / this.numberOfPointsOnEachCircleForRoundPillars;

			//loop over all points
			for (int runPoints = 0; runPoints < this.numberOfPointsOnEachCircleForRoundPillars; runPoints++) {
				//calculate current angle
				double currAngle = samplingDistanceAngle * runPoints;
				//calculate current position
				VectorD currPositionOnCircle = new VectorD(Math.Cos(currAngle) * 0.5 * diameterOfCircle,
														   Math.Sin(currAngle) * 0.5 * diameterOfCircle);
				//apply shift by center of circle
				currPositionOnCircle += positionOfCircle;
				//add position to vertic list
				vertices.Add(new NamedPolygonVertex(runPoints.ToString(), currPositionOnCircle));
			}
			//return generated polygon
			return new SimplePolygon(vertices);
		}

		/// <summary>
		/// support method to generate a polygon for a square pillar
		/// </summary>
		/// <param name="positionOfSquare">the position of the square</param>
		/// <param name="diameterOfSquare">the diameter of the square</param>
		/// <returns>the shape as simple polygon</returns>
		private SimplePolygon generateSquareAsPolygon(VectorD positionOfSquare,
													  double diameterOfSquare) {
			//generate rectangle
			RectangularRegion rectRegion = new RectangularRegion(positionOfSquare,
																 diameterOfSquare,
																 diameterOfSquare,
																 0,
																 false);
			//return generated simple polygon
			return new SimplePolygon(rectRegion);
		}

		/// <summary>
		/// Converts a polygon in physical coordinates into an array of integers.
		/// The integers give the coordinates of the vertices of the polygon in multiples of the conversion factor <i>c</i> (x-position of first vertex,
		/// y-position of first vertex, x-position of second vertex, ...).
		/// </summary>
		/// <param name="polygon">The polygon to convert. It is assumed that the polygon is open (i.e. first and last vertex are not identical).</param>
		/// <param name="conversionFactor">The conversion factor <i>c</i>.</param>
		/// <returns>An array of integers describing a closed polygon (i.e. first and last vertex are identical). </returns>
		private int[] convertPolygonToIntegerArray(SimplePolygon polygon, double conversionFactor) {
			if (polygon == null) {
				throw new ArgumentException("Polygon was not set up correctly.");
			}
			if (polygon.Vertices.Count <= 0) {
				throw new ArgumentException("Polygon was not set up correctly.");
			}

			List<VectorD> verticesDouble = new List<VectorD>();
			for (int runVertices = 0; runVertices < polygon.Vertices.Count; runVertices++) {
				verticesDouble.Add(polygon.Vertices[runVertices].Coordinates);
			}
			verticesDouble.Add(verticesDouble[0]); // close the polygon.

			List<int> vertices = new List<int>(polygon.Vertices.Count * 2);
			foreach (VectorD vertex in verticesDouble) {
				int vertexX = MathFunctions.RoundToInt(vertex.X / conversionFactor);
				int vertexY = MathFunctions.RoundToInt(vertex.Y / conversionFactor);
				vertices.AddRange(new int[2] { vertexX, vertexY });
			}

			return vertices.ToArray();
		}

	}
}
